package org.jeecg.config.binlog;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSONObject;
import com.github.shyiko.mysql.binlog.event.EventType;
import io.swagger.annotations.ApiModelProperty;
import org.jeecg.common.aspect.annotation.AutoRecordLog;
import org.jeecg.common.aspect.annotation.AutoRecordStatusLog;
import org.jeecg.common.util.FieldUtil;
import org.jeecg.common.util.RedisUtil;
import org.jeecg.modules.log.service.ISysDataLogService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**
 * @author gbstart
 * @create 2024-06-03 15:12
 */
@Component
public class ChangeDataAutoRecordtwo {

    @Autowired
    private ISysDataLogService iSysDataLogService;

    @Autowired
    private RedisUtil redisUtil;


    private void update(BinLogItem item,String table) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchFieldException {
        if(!redisUtil.hasKey(table)){
            return;
        }
        Object o = redisUtil.get(table);
        Map map0 = BeanUtil.toBean(o, Map.class);
        Class<?> clazz = Class.forName(map0.get("clazz").toString());
        Object oldObj = loadData(item.getBefore(), clazz);
        Object newObj = loadData(item.getAfter(), clazz);
        sendMsg(map0, oldObj,newObj,table,"update");
    }

    private void insert(BinLogItem item,String table) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchFieldException {
        if(!redisUtil.hasKey(table)){
            return;
        }
        Object o = redisUtil.get(table);
        Map map0 = BeanUtil.toBean(o, Map.class);
        Class<?> clazz = Class.forName(map0.get("clazz").toString());
        Object newObj = loadData(item.getAfter(), clazz);
        sendMsg(map0, null,newObj,table,"insert");
    }

    private void delete(BinLogItem item,String table) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchFieldException {
        if(!redisUtil.hasKey(table)){
            return;
        }
        Object o = redisUtil.get(table);
        Map map0 = BeanUtil.toBean(o, Map.class);
        Class<?> clazz = Class.forName(map0.get("clazz").toString());
        Object newObj = loadData(item.getAfter(), clazz);
        sendMsg(map0, null,newObj,table,"delete");
    }

    private void sendMsg(Map map0,Object oldObj,Object newObj,String table,String type) throws NoSuchFieldException, IllegalAccessException, ClassNotFoundException {
        boolean hasParent = (boolean) map0.get("existParent");
        String parentId = "";
        String parentTable = "";
        if(hasParent){
            Field parentField = newObj.getClass().getDeclaredField((String) map0.get("parentField"));
            parentField.setAccessible(true);
            parentId = (String) parentField.get(newObj);
            parentTable = (String) map0.get("parentTable");
        }
        Field idField = newObj.getClass().getDeclaredField("id");
        idField.setAccessible(true);
        String id = (String) idField.get(newObj);
        String logtime = null;
        String parentLogtime = null;
        if(FieldUtil.isPropertyExist( newObj.getClass(),"logtime")){
            Field logtimeField = newObj.getClass().getDeclaredField("logtime");
            logtimeField.setAccessible(true);
            logtime = (String) logtimeField.get(newObj);
        }
        if(FieldUtil.isPropertyExist( newObj.getClass(),"parentLogtime")){
            Field parentLogtimeField = newObj.getClass().getDeclaredField("parentLogtime");
            parentLogtimeField.setAccessible(true);
            parentLogtime = (String) parentLogtimeField.get(newObj);
        }
        Map map = differenceSet(oldObj, newObj);
        iSysDataLogService.addDataLog(table,id, map!=null?JSONObject.toJSONString(map):"",
                type,oldObj!=null?JSONObject.toJSONString(oldObj):null,newObj!=null?JSONObject.toJSONString(newObj):null,parentId,parentTable,logtime,parentLogtime);
    }

    /**
     * 接受变动信息
     * @param item
     * @param table
     * @throws NoSuchFieldException
     * @throws ClassNotFoundException
     * @throws InstantiationException
     * @throws IllegalAccessException
     */
    public void messageChange(BinLogItem item,String table) throws NoSuchFieldException, ClassNotFoundException, InstantiationException, IllegalAccessException {
        EventType eventType = item.getEventType();
        if(EventType.isWrite(eventType)){
            insert(item,table);
        }else if(EventType.isUpdate(eventType)){
            update(item, table);
        }else{
            delete(item, table);
        }
    }




    private Object loadData(Map<String, Serializable> content,Class clazz) throws InstantiationException, IllegalAccessException {
        Object obj = clazz.newInstance();
        Field[] declaredFields = obj.getClass().getDeclaredFields();
        Set<String> strings = content.keySet();
        for (Field field : declaredFields) {
            String name = field.getName();
            Serializable serializable = content.get(StrUtil.toUnderlineCase(name));
            if(serializable!=null){
                setValue(field,obj,serializable);
            }
        }
        return obj;
    }



    public void setValue(Field field,Object obj,Object value) throws IllegalAccessException {
        field.setAccessible(true);
        if(value == null){
            field.set(obj,null);
        }else if (field.getType() == String.class) {
            field.set(obj,String.valueOf(value));
        } else if (field.getType() == Integer.class) {
            field.set(obj, Integer.parseInt(value.toString()));
        } else if (field.getType() == Double.class) {
            field.set(obj, Double.parseDouble(value.toString()));
        } else if (field.getType() == Date.class) {
            if(value instanceof Long){
                field.set(obj,new Date(Long.parseLong(value.toString())));
            }else{
                field.set(obj, DateUtil.format(new Date(value.toString()), "yyyy-MM-dd HH:mm:ss"));
            }
        } else if (field.getType().isPrimitive()) {
            //throw new IllegalArgumentException("Primitive type with no default value: " + type.getName());
        } else {
            field.set(obj,value);
        }
    }

    public Map differenceSet(Object oldObj,Object newObj) throws IllegalAccessException {
        if(oldObj==null || newObj==null){
            return null;
        }
        Map map = new HashMap();
        Class<?> aClass = oldObj.getClass();
        AutoRecordLog annotation = aClass.getAnnotation(AutoRecordLog.class);
        String[] ignoreCloumns = annotation.ignoreCloumn();

        Field[] olddeclaredFields = aClass.getDeclaredFields();
        Field[] newdeclaredFields = newObj.getClass().getDeclaredFields();
        for (Field olddeclaredField : olddeclaredFields) {
            String name = olddeclaredField.getName();
            olddeclaredField.setAccessible(true);
            Object oldVal = olddeclaredField.get(oldObj);
            if(ArrayUtil.contains(ignoreCloumns, name)){
               continue;
            }
            for (Field newdeclaredField : newdeclaredFields) {
                String newName = newdeclaredField.getName();
                newdeclaredField.setAccessible(true);
                Object newVal = newdeclaredField.get(newObj);
                if(name.equals(newName)&& !ObjUtil.equal(oldVal,newVal)){
                    if(newdeclaredField.isAnnotationPresent(ApiModelProperty.class)){
                        String value = newdeclaredField.getAnnotation(ApiModelProperty.class).value();
                        if(newdeclaredField.isAnnotationPresent(AutoRecordStatusLog.class)){
                            String[] v = newdeclaredField.getAnnotation(AutoRecordStatusLog.class).value();
                            map.put(value,v[Integer.parseInt(oldVal.toString())] +" -> "+v[Integer.parseInt(newVal.toString())]);
                        }else{
                            map.put(value,this.fomatData(olddeclaredField,oldVal)  +" -> "+ this.fomatData(newdeclaredField,newVal));
                        }
                    }else{
                        map.put(newName,this.fomatData(olddeclaredField,oldVal)  +" -> "+ this.fomatData(newdeclaredField,newVal));
                    }
                }
            }
        }
        return map;
    }

    private Object fomatData(Field field,Object value){
         if (field.getType() == Date.class) {
            if(value instanceof Long){
                return DateUtil.format(new Date(value.toString()), "yyyy-MM-dd HH:mm:ss");
            }else{
                return DateUtil.format((Date) value, "yyyy-MM-dd HH:mm:ss");
            }
        }
        return value;
    }



//    private  <T> T getDefaultValue(Field field,Object value) {
//        if (field.getType() == String.class) {
//            return (T) ;
//        } else if (field.getType() == int.class) {
//            return (T)Integer.valueOf(0);
//        } else if (field.getType() == double.class) {
//            return (T)Double.valueOf(0.0);
//        } else if (field.getType() == boolean.class) {
//            return (T)Boolean.FALSE;
//        } else if (field.getType().isPrimitive()) {
//            throw new IllegalArgumentException("Primitive type with no default value: " + type.getName());
//        } else {
//            // 对于其他类型，返回null
//            return null;
//        }
//    }



}
